package per.qiao.simplerest.scanner;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.env.ConfigurablePropertyResolver;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.*;
import per.qiao.simplerest.proxy.RestServiceBeanFactory;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;

/**
 * Company: 上海数惠系统技术有限公司
 * Department: 数据中心
 * Date: 2020-05-29 16:19
 *
 * @Author: qyp
 * Email: qiaoyp@dist.com.cn
 * Desc:
 */
public abstract class AbstractRestServiceScanner implements ServiceScanner {

    final String CLASS_FILE_SUFFIX = ".class";
    final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    private final MutablePropertySources propertySources = new MutablePropertySources();

    private PathMatcher pathMatcher = new AntPathMatcher();

    private final ConfigurablePropertyResolver propertyResolver =
            new PropertySourcesPropertyResolver(this.propertySources);


    @Override
    public Set<Class> scanner(String scannerPackage) {
        ClassLoader defaultClassLoader = ClassUtils.getDefaultClassLoader();
        Set<Class> allMetcher = new LinkedHashSet<>(10);
        try {
            Enumeration<URL> resources = ClassUtils.getDefaultClassLoader().getResources(scannerPackage);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                String filePath = url.getFile();
                File rootFile = new File(filePath);
                Set<String> matchingFileClassNames = retrieveMatchingFiles(rootFile, DEFAULT_RESOURCE_PATTERN);
//                Set<Resource> result = new LinkedHashSet<>(matchingFiles.size());
                for (String file : matchingFileClassNames) {
//                    FileSystemResource fileSystemResource = new FileSystemResource(file);
                    try {
                        Class<?> aClass = defaultClassLoader.loadClass(file);
                        if (supports(aClass)) {
                            allMetcher.add(aClass);
                        }
                    } catch (ClassNotFoundException e) {
                        // nothing to do
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return innerScanner(allMetcher);
    }



    protected Set<String> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
        if (!rootDir.exists()) {
            return Collections.emptySet();
        }
        if (!rootDir.isDirectory()) {
            return Collections.emptySet();
        }
        if (!rootDir.canRead()) {
            return Collections.emptySet();
        }
        String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
        if (!pattern.startsWith("/")) {
            fullPattern += "/";
        }
        fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
        Set<String> result = new LinkedHashSet<>(8);
        doRetrieveMatchingFiles(fullPattern, rootDir, result);
        return result;
    }

    protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<String> result) {

        for (File content : listDirectory(dir)) {
            String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
            if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
                if (content.canRead()) {
                    doRetrieveMatchingFiles(fullPattern, content, result);
                }
            }
            if (getPathMatcher().match(fullPattern, currPath)) {
                int index = currPath.lastIndexOf("classes");
                String className = StringUtils.replace(currPath.substring(index + 8), "/", ".");
                result.add(className.replace(CLASS_FILE_SUFFIX, ""));
            }
        }
    }



    private PathMatcher getPathMatcher() {
        return pathMatcher;
    }

    protected File[] listDirectory(File dir) {
        File[] files = dir.listFiles();
        if (files == null) {
            return new File[0];
        }
        Arrays.sort(files, Comparator.comparing(File::getName));
        return files;
    }

    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(resolveRequiredPlaceholders(basePackage));
    }

    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        return this.propertyResolver.resolveRequiredPlaceholders(text);
    }

    protected abstract Set<Class> innerScanner(Set<Class> allMetcher);

    /*private ResourcePatternResolver resourcePatternResolver;
    private MetadataReaderFactory metadataReaderFactory;

    private Environment environment;

    private Set<Class> restService = new LinkedHashSet<>();

    @Override
    public Set<Class> scanner(String basePackage) {
        // Set<Class> allMetcher = new LinkedHashSet<>();
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + DEFAULT_RESOURCE_PATTERN;
        try {
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                    String className = metadataReader.getClassMetadata().getClassName();
                    Class<?> clazz;
                    try {
                        clazz = Class.forName(className);
                        if (supports(clazz)) {
                            restService.add(clazz);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        innerScanner(restService);
        return restService;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    }

    protected String resolveBasePackage(String basePackage) {
        return ClassUtils.convertClassNameToResourcePath(this.environment.resolveRequiredPlaceholders(basePackage));
    }


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {

        scannerService();

        if (ObjectUtils.isEmpty(restService)) {
            return;
        }
        for (Class beanClazz : restService) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
            GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);
            definition.setBeanClass(RestServiceBeanFactory.class);
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    protected abstract Set<Class> innerScanner(Set<Class> allMetcher);

    public void scannerService() {
        List<String> basepackages = getBasePackages();
        for (String servicePackage : basepackages) {
            Set<Class> scanner = this.scanner(servicePackage);
            if (!ObjectUtils.isEmpty(scanner)) {
                restService.addAll(scanner);
            }
        }
    }

    protected List<String> getBasePackages() {
        List<String> packags = new ArrayList<>();
        packags.add("per/qiao/simplerest/");
        return packags;
    }*/

    /*protected void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    protected void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
    }*/
}
